package algebra.rewriting.kfree;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import algebra.op.*;
import algebra.query.pd.*;

import kb.DescriptionLogicsKB;
import kb.TBox;

import option.KBVocabulary;
import reasoner.KBReason;

import utils.exception.CoreReasoningException;
import utils.exception.SyntaxParsingException;

import entities.KBEntity;
import entities.concept.AndConcept;
import entities.concept.Concept;
import entities.concept.DomainConcept;
import entities.concept.DomainConceptEQ;
import entities.concept.ExistsConcept;
import entities.role.Role;

public class KFreeApproximate {


	//check the semantic symbol tilde~>
	public static boolean checkSetEQ(ProjDesc left, AlgebraicOP subquery){
		try {
			ProjDesc right = repLangAsPd(subquery);
			return isIncluded(left, right);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}
	
	//for pure queries only
	private static ProjDesc repLangAsPd(AlgebraicOP op) throws SyntaxParsingException{
		if(op.isPure()){
			if(op instanceof PrimaryOP){
				return new PdConcept(KBVocabulary.CONCEPT_TOP);
			}else if(op instanceof JoinOP){
				ProjDesc pd1 = repLangAsPd(((JoinOP) op).getFirstSubQuery());
				ProjDesc pd2 = repLangAsPd(((JoinOP) op).getSecondSubQuery());
				if(pd1 == null || pd2 == null){
					throw new SyntaxParsingException("Operation parsing as Pd error: "+op);
				}
				return new PdConjunction(pd1, pd2);
			}else if(op instanceof ProjectionOP){
				return ((ProjectionOP) op).getPrjDesc();
			}else if(op instanceof SelectionOP){
				return repLangAsPd(((SelectionOP) op).getSubquery());
			}else if(op instanceof ScanOP){
				return ((ScanOP) op).getSecondaryIndex().getPdi();
			}
		}
		return null;
	}
	
	private static int count = 0;
	private static DecoTree decompTree(ProjDesc pd) throws SyntaxParsingException{
		
		DecoTree tree = new DecoTree();
		if(pd instanceof PdConcept){
			tree.setRoot( ++ count);
			tree.insertTriple(count, ((PdConcept) pd).getConcept(), ++count);
		}else if(pd instanceof PdFeature){
			tree.setRoot( ++ count);
			Role f = ((PdFeature) pd).getFeature();
			DomainConcept<Role, String> fstar = new DomainConceptEQ<Role, String>(f, 
					KBVocabulary.AnyStringInKB);
			tree.insertTriple(count, fstar, ++count);
		}else if(pd instanceof PdConjunction){
			DecoTree tree1 = decompTree(((PdConjunction) pd).getSubPd1());
			DecoTree tree2 = decompTree(((PdConjunction) pd).getSubPd2());
			tree1.mergeInto(tree2);
			tree = tree2;
		}else if(pd instanceof PdRole){
			DecoTree tree3 = decompTree(((PdRole) pd).getSubPd());
			tree3.addNewRoot(++count,  ((PdRole) pd).getRole());
			tree= tree3;
		}
		return tree;
	}
	
	private static boolean isIncludedConcept(Concept c, DecoTree tree, Integer head) 
			throws CoreReasoningException{
		if(tree.hasNodeLabel(head, c)){
			return true;
		}
		DomainConcept<Role, String> fstar = null;
		if(c instanceof DomainConceptEQ){
			DomainConceptEQ<?, ?> fk = (DomainConceptEQ<?, ?>) c;
			if(fk.getRealValue() != null){
				Role f = (Role) fk.getRealFeature();
				fstar = new DomainConceptEQ<Role, String>(f, 
						KBVocabulary.AnyStringInKB);
				if(tree.hasNodeLabel(head, fstar)){
					return true;
				}
			}
		}
		//test general concept
		for(KBEntity e:tree.getNodeLabels(head)){
			if(e instanceof Concept){
				Concept ec = (Concept) e;
				KBReason reasoner = new KBReason();
				DescriptionLogicsKB kb = DescriptionLogicsKB.newKBInstance(
						new TBox(), null);
				if(reasoner.subsumesGuarded(c, ec, kb) 
						&& reasoner.subsumesGuarded(ec, c, kb)){
					return true;
				}
			}
		}
		//test And concept
		if(c instanceof AndConcept){
			AndConcept ac = (AndConcept) c;
			for(Concept and: ac.getConceptSet()){
				ProjDesc pdc = new PdConcept(and);
				if(!isIncluded(pdc, tree, head)){
					return false;
				}
			}
			return true;
		}
		if(c instanceof ExistsConcept){
			ExistsConcept ex = (ExistsConcept) c;
			Concept ec = ex.getFiller();
			ProjDesc pdc = new PdConcept(ec);
			return isIncludedRole(ex.getRole(), pdc, tree, head);
		}
		return false;
	}
	
	private static boolean isIncludedRole(Role r, ProjDesc sub, DecoTree tree, Integer head) 
			throws CoreReasoningException{
		Set<Integer> ends = tree.getAllEnds(head, r);
		if(ends == null){
			return false;
		}else{
			for(Integer end:ends){
				if(isIncluded(sub, tree, end)){
					return true;
				}
			}
		}
		return false;
	}
	
	public static boolean isIncluded(ProjDesc pd1, ProjDesc pd2) 
			throws SyntaxParsingException, CoreReasoningException{
		if(pd1 == null  || pd2 == null){
			throw new SyntaxParsingException("Null Pd supplied for checking: "+pd1+"; "+pd2);
		}
		DecoTree tree = decompTree(pd2);
		return isIncluded(pd1, tree, tree.root);
	}
	
	private static boolean isIncluded(ProjDesc pd, DecoTree tree, Integer head) 
			throws CoreReasoningException{
		if(pd instanceof PdConcept){
			Concept pc = ((PdConcept) pd).getConcept();
			return isIncludedConcept(pc, tree, head);
		}else if(pd instanceof PdFeature){
			Role f = ((PdFeature) pd).getFeature();
			DomainConcept<Role, String> fstar = new DomainConceptEQ<Role, String>(f, 
					KBVocabulary.AnyStringInKB);
			if(tree.hasNodeLabel(head, fstar)){
				return true;
			}
		}else if(pd instanceof PdConjunction){
			return isIncluded(((PdConjunction) pd).getSubPd1(), tree, head)
					&& isIncluded(((PdConjunction) pd).getSubPd2(), tree, head);
		}else if(pd instanceof PdRole){
			return isIncludedRole(((PdRole) pd).getRole(), ((PdRole) pd).getSubPd(), tree, head);
		}
		return false;
	}
	
	
	static class DecoTree{
		public Integer root;
		public Map<Integer, Map<KBEntity, Set<Integer>>> tree = 
				new HashMap<Integer, Map<KBEntity, Set<Integer>>>();
		
		public void insertTriple(Integer l, KBEntity label, Integer r) {
			Map<KBEntity, Set<Integer>> inner = null;
			if(tree.containsKey(l)){
				inner = tree.get(l);
			}else{
				inner = new HashMap<KBEntity, Set<Integer>>();
			}
			Set<Integer> ends = null;
			if(inner.containsKey(label) ){
				ends = inner.get(label);
			}else{
				ends = new HashSet<Integer>();
			}
			ends.add(r);
			inner.put(label, ends);
			tree.put(l, inner);
		}
		
		public Set<KBEntity> getNodeLabels(Integer head){
			Set<KBEntity> ret = new HashSet<KBEntity>();
			if(tree.containsKey(head)){
				ret.addAll(tree.get(head).keySet());
			}
			return ret;
		}
		
		public boolean hasNodeLabel(Integer head, KBEntity label){
			if(tree.containsKey(head)){
				return tree.get(head).containsKey(label);
			}
			return false;
		}
		
		public Set<Integer> getAllEnds(Integer head, KBEntity label){
			if(tree.containsKey(head)){
				return tree.get(head).get(label);
			}
			return null;
		}
	
		public void addNewRoot(Integer newroot, KBEntity c) throws SyntaxParsingException{
			Integer oldroot = this.root;
			this.root = newroot;
			//add a new edge that points to old root
			this.insertTriple(newroot, c, oldroot);
		}
		
		public void mergeInto(DecoTree n1) throws SyntaxParsingException{
			Integer oldroot = this.root;
			//reset root
			this.root = n1.root;
			if(this.tree.containsKey(oldroot)){
				//move all edges staring from old root : the rest are just copied
				for(KBEntity c:this.tree.get(oldroot).keySet()){
					Set<Integer> end = this.tree.get(oldroot).get(c);
					for(Integer e:end){
						n1.insertTriple(root, c, e);
					}
				}
				//remove this old root from edges
				this.tree.remove(oldroot);
			}
			//add other edges
			n1.tree.putAll(tree);
		}
		
		public void setRoot(Integer root){
			this.root = root;
		}
		
		
	}
	

}
